package lxs.swift.collector.wrap;

import lxs.swift.fn.Supply;
import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public interface Trouble<T, E extends Throwable> extends Wrapper<T, E, Trouble<T, E>>, Serializable {

    T unwrap() throws E;

    boolean isOk();

    boolean isErr();

    @Override
    default T unwrapOr(@NotNull Supplier<T> supplier) {
        Objects.requireNonNull(supplier);
        if (!isOk()) {
            return supplier.get();
        }
        Ok<T, E> ok = (Ok<T, E>) this;
        return ok.value;
    }

    @Override
    default Trouble<T, E> and(@NotNull Trouble<T, E> trouble) {
        Objects.requireNonNull(trouble);
        if (this.isOk()) {
            return trouble;
        }
        return this;
    }


    @Override
    default Trouble<T, E> or(@NotNull Trouble<T, E> wrapper) {
        Objects.requireNonNull(wrapper);
        if (isOk()) {
            return this;
        }
        return wrapper;
    }

    @Override
    default Trouble<T, E> replace(boolean bool, @NotNull Supplier<T> supplier) {
        Objects.requireNonNull(supplier);
        if (!bool) {
            return this;
        }
        T v = supplier.get();
        return Trouble.ok(v);
    }


    @Override
    default Trouble<T, E> copy() {
        if (this.isOk()) {
            Ok<T, E> ok = (Ok<T, E>) this;
            return new Ok<>(ok.value);
        }
        Err<T, E> err = (Err<T, E>) this;
        return Trouble.err(err.error);
    }


    default <R> Trouble<R, E> map(@NotNull Function<T, R> mapper) {
        Objects.requireNonNull(mapper);
        if (this.isOk()) {
            Ok<T, E> ok = (Ok<T, E>) this;
            R r = mapper.apply(ok.value);
            return Trouble.ok(r);
        }
        return (Trouble<R, E>) this;
    }

    default <RE extends Throwable> Trouble<T, RE> mapError(@NotNull Function<E, RE> mapper) {
        Objects.requireNonNull(mapper);
        if (isOk()) return ((Ok) this);
        Err<T, E> err = (Err<T, E>) this;
        RE re = mapper.apply(err.error);
        return Trouble.err(re);
    }


    default Trouble<T, E> replaceError(boolean bool, @NotNull Supplier<E> supplier) {
        Objects.requireNonNull(supplier);
        if (!bool) {
            return this;
        }
        E e = supplier.get();
        return Trouble.err(e);
    }

    default Trouble<Void, E> toVoid() {
        if (this.isOk()) {
            return new Ok<>(null);
        }
        return ((Err<Void, E>) this);
    }


    default Option<T> ok() {
        if (this.isOk()) {
            Ok<T, E> ok = (Ok<T, E>) this;
            return Option.some(ok.value);
        }
        return Option.none();
    }

    default Option<E> err() {
        if (this.isOk()) {
            return Option.none();
        }
        Err<T, E> err = (Err<T, E>) this;
        return Option.some(err.error);
    }

    default E unwrapError() {
        if (isOk()) {
            throw new RuntimeException("called `Trouble::unwrapError()` on a `Ok` value");
        }
        Err<T, E> err = (Err<T, E>) this;
        return err.error;
    }

    default E expectError(String errMsg) {
        if (isOk()) {
            throw new RuntimeException(errMsg);
        }
        Err<T, E> err = (Err<T, E>) this;
        return err.error;
    }

    default <EE extends Throwable> E expectError(String errMsg, @NotNull Function<String, EE> func) throws EE {
        Objects.requireNonNull(func);
        if (isOk()) {
            throw func.apply(errMsg);
        }
        Err<T, E> err = (Err<T, E>) this;
        return err.error;
    }

    default <EE extends Throwable> Trouble<T, EE> is(@NotNull Predicate<T> predicate, @NotNull Supplier<EE> supplier) {
        Objects.requireNonNull(predicate);
        Objects.requireNonNull(supplier);
        if (this.isOk()) {
            Ok<T, E> ok = (Ok<T, E>) this;
            if (predicate.test(ok.value)) {
                return Trouble.ok(ok.value);
            }
        }
        EE ee = supplier.get();
        return Trouble.err(ee);
    }

    default <RR> RR matchMap(Function<T, RR> ok, Function<E, RR> err) {
        if (this.isOk()) {
            Ok<T, E> okObj = (Ok<T, E>) this;
            return ok.apply(okObj.value);
        }
        return err.apply(unwrapError());
    }

    default void matchRun(Consumer<T> ok, Consumer<E> err) {
        if (this.isOk()) {
            Ok<T, E> okObj = (Ok<T, E>) this;
            ok.accept(okObj.value);
            return;
        }
        err.accept(unwrapError());
    }

    default <RR> RR selected(RR ok, RR err) {
        if (this.isOk()) {
            return ok;
        }
        return err;
    }

    static <E extends Throwable> Trouble<Void, E> nil() {
        return new Ok<>(null);
    }

    static <T, E extends Throwable> Trouble<T, E> ok(@NotNull T value) {
        Objects.requireNonNull(value);
        return new Ok<>(value);
    }

    static <T, E extends Throwable> Trouble<T, E> err(@NotNull E error) {
        Objects.requireNonNull(error);
        return new Err<>(error);
    }

    static <T, E extends Throwable> Trouble<T, E> of(@NotNull Supply<T, E> supply) {
        Objects.requireNonNull(supply);
        try {
            T t = supply.get();
            return new Ok<>(t);
        } catch (Throwable e) {
            return new Err<>(((E) e));
        }
    }

    class Ok<T, E extends Throwable> implements Trouble<T, E> {
        public final T value;

        private Ok(T value) {
            this.value = value;
        }

        @Override
        public T unwrap() {
            return value;
        }

        @Override
        public boolean isOk() {
            return true;
        }

        @Override
        public boolean isErr() {
            return false;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Ok<?, ?> ok = (Ok<?, ?>) o;
            return Objects.equals(value, ok.value);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }

        @Override
        public String toString() {
            String typeName = value.getClass().getTypeName();
            if (value instanceof CharSequence) {
                return typeName + ":Trouble.Ok(\"" + value + "\")";
            }
            return typeName + ":Trouble.Ok(" + value + ")";
        }
    }

    class Err<T, E extends Throwable> implements Trouble<T, E> {
        public final E error;

        private Err(E error) {
            this.error = error;
        }

        @Override
        public T unwrap() {
            throw new RuntimeException("called `Trouble::unwrap()` on a `Err` value", error);
        }

        @Override
        public boolean isOk() {
            return false;
        }

        @Override
        public boolean isErr() {
            return true;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Err<?, ?> err = (Err<?, ?>) o;
            return Objects.equals(error, err.error);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(error);
        }

        @Override
        public String toString() {
            String typeName = error.getClass().getTypeName();
            return typeName + ":Trouble.Err(" + error.getMessage() + ")";
        }
    }


}
